﻿using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebApp.Entities;
using WebApp.Models;

namespace WebApp.Services
{
    /// <summary>
    /// PowerHost对外执行
    /// </summary>
    public interface IPowerHostService
    {
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        Task StartAsync();
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        Task StopAsync();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        Task<IoTCmdResult> DeviceCommandAsync(IoTCmdReq req);
        /// <summary>
        /// 查询命令执行结果
        /// </summary>
        /// <param name="queryId"></param>
        /// <returns></returns>
        IoTCmdResult QueryDeviceCommand(string queryId);
    }
    /// <summary>
    /// BackService 服务
    /// </summary>
    public class PowerHostedService : BackgroundService, IPowerHostService
    {
        #region private
        private readonly ILogger<PowerHostedService> _logger;
        private readonly IServiceProvider _serviceProvider;
        private readonly IOptions<AppSettings> _settings;
        private readonly Socketor _socketor;
        readonly IHttpClientFactory _httpClientFactory;
        //采集器编号，Socket连接
        private static readonly ConcurrentDictionary<string, Socket> _Clients = new ConcurrentDictionary<string, Socket>();
        Task taskAccept;
        Task taskLoopMeter;
        Task taskLoopOperation;
        //private Semaphore m_maxNumberAcceptedClients; //限制访问接收连接的线程数，用来控制最大并发数
        Socket socket;

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="serviceProvider"></param>
        /// <param name="settings"></param>
        /// <param name="socketor"></param>
        /// <param name="httpClientFactory"></param>
        public PowerHostedService(ILogger<PowerHostedService> logger,
            IOptions<AppSettings> settings, IServiceProvider serviceProvider,
            Socketor socketor, IHttpClientFactory httpClientFactory)
        {
            _socketor = socketor;
            _logger = logger;
            _serviceProvider = serviceProvider;
            _settings = settings;
            _httpClientFactory = httpClientFactory;
            _CancelSource = new CancellationTokenSource();
        }
        private CancellationTokenSource _CancelSource;

        #region 启动停止
        public async Task StartAsync()
        {
            await StartAsync(_CancelSource.Token);
            Console.WriteLine("Start");
        }

        public async Task StopAsync()
        {
            await StopAsync(_CancelSource.Token);
            Console.WriteLine("Stop");
        }
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            _CancelSource = new CancellationTokenSource();

            return base.StartAsync(_CancelSource.Token);
        }
        public override Task StopAsync(CancellationToken cancellationToken)
        {
            _CancelSource.Cancel();
            return base.StopAsync(_CancelSource.Token);
        }
        #endregion

        #region 任务守护
        /// <summary>
        /// 
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation($"PowerHostedService is starting.");
            _CancelSource.Token.Register(() => _logger.LogWarning($"PowerHostedService is stopping 1."));

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    if (taskAccept == null || taskAccept.IsFaulted || taskAccept.Status == TaskStatus.RanToCompletion)
                    {
                        taskAccept = Task.Run(StartAccept, _CancelSource.Token);
                        _logger.LogDebug($"监听心跳 is starting.");
                    }

                    if (taskLoopMeter == null || taskLoopMeter.IsFaulted || taskLoopMeter.Status == TaskStatus.RanToCompletion)
                    {
                        taskLoopMeter = Task.Run(DoLoopMeters, _CancelSource.Token);
                        _logger.LogDebug($"循环电表队列 is starting.");
                    }

                    if (taskLoopOperation == null || taskLoopOperation.IsFaulted || taskLoopOperation.Status == TaskStatus.RanToCompletion)
                    {
                        taskLoopOperation = Task.Run(DoLoopOperations, _CancelSource.Token);
                        _logger.LogDebug($"循环操作队列 is starting.");
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "ExecuteAsync异常");
                }

                await Task.Delay(1000 * 5, _CancelSource.Token);
            }
            foreach (var socket in _Clients)
            {
                socket.Value.Shutdown(SocketShutdown.Both);
                socket.Value.Close();
                socket.Value.Dispose();
            }
            _logger.LogError($"PowerHostedService is stopping 2.");
        }
        #endregion

        #region 迎接新连接

        /// <summary>
        /// 启动监听Socket连接
        /// </summary>
        private void StartAccept()
        {
            try
            {
                IPEndPoint iep;
                if (IPAddress.TryParse(_settings.Value.IPAddress, out IPAddress myip))
                    iep = new IPEndPoint(myip, _settings.Value.Port);
                else
                    iep = new IPEndPoint(IPAddress.Any, _settings.Value.Port);

                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                socket.Bind(iep);

                this.socket.Listen(_settings.Value.ListenCount);

                while (true)
                {
                    Task.Delay(100).Wait();

                    _logger.LogInformation($"--开始监听新链接--");

                    Socket newSocket = this.socket.Accept();

                    _logger.LogInformation($"--新Socket：{newSocket.RemoteEndPoint}--");

                    Task.Factory.StartNew(OnAccept, newSocket, _CancelSource.Token);

                }
            }
            catch(Exception ex)
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
                
                _logger.LogError(ex, "启动监听Socket连接 异常");
            }
            finally
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }
        }
        #endregion

        #region 守护连接池
        /// <summary>
        /// 处理连接上的Socket客户端
        /// </summary>
        /// <param name="socket"></param>
        private void OnAccept(object socket)
        {
            Socket newSocket = (Socket)socket;

            newSocket.NoDelay = true;
            newSocket.LingerState = new LingerOption(true, 10);
            newSocket.ReceiveTimeout = newSocket.SendTimeout = _settings.Value.Timeout;
            newSocket.NoDelay = true;
            try
            {
                byte[] byteMessage;
                byteMessage = new byte[128];

                if (!newSocket.Connected)
                {
                    newSocket.Close();
                    return;
                }

                var len = newSocket.Receive(byteMessage);
                string code = Encoding.UTF8.GetString(byteMessage, 0, len);
                var collectorcode = code.Split(',')[0].Trim();

                if (_Clients.TryRemove(collectorcode, out Socket old))
                {
                    _logger.LogWarning($"--采集器 {collectorcode} 重新连接--");
                }
                _Clients.TryAdd(collectorcode, newSocket);
                _logger.LogInformation($"--收到Socket连接 {code} - Socket数量：{_Clients.Count}--");

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "OnAccept");
            }
            finally
            {

            }
        }
        #endregion

        #region 执行命令队列
        /// <summary>
        /// 执行队列中的命令
        /// </summary>
        private void DoLoopOperations()
        {
            var dbService = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();
            while (true)
            {
                var operations = dbService.GetOperations(_settings.Value.BathCount);
                if (operations.Count == 0 || _Clients.Count == 0)
                {
                    Task.Delay(1000 * 10).Wait();
                    continue;
                }
                var group = operations.ToLookup(p => p.CollectorCode);

                var pll = Parallel.ForEach(group, (ops, pl) =>
                {
                    if (_Clients.TryRemove(ops.Key, out Socket mySocket))//存在使用，否则等待
                    {
                        var service = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();
                        foreach (var op in ops)
                        {
                            var result = _socketor.Send(mySocket, op.CollectorCode, op.MeterCode, op.Command, op.Password);
                            if (result.Result && result.MeterCode == op.MeterCode)
                            {
                                op.Status = IoTCmdStatus.SUCCESSFUL;
                            }
                            else
                            {
                                op.Status = op.TryCount >= 3 ? IoTCmdStatus.FAILED : IoTCmdStatus.UnSend;
                                //操作错误记录日志
                                var log = new ExceptionLogDto
                                {
                                    AppId = op.AppId,
                                    CollectorCode = op.CollectorCode,
                                    MeterCode = op.MeterCode,
                                    CallbackUrl = "",
                                    Command = op.Command,
                                    Message = result.Message,
                                    StackTrace = "",
                                    RequestId = ""
                                };
                                service.AddExceptionLog(log);


                            }
                            var meter = service.GetMeter(op.MeterCode);
                            service.UpdateOperation(op.Id, op.Status);
                            _ = CommandChangedAsync(op, meter.Status, DateTime.Now, result.Power, result.Message);
                        }
                        _Clients.TryAdd(ops.Key, mySocket);//完璧归赵
                    }
                });
            }
        }
        #endregion

        #region 循环读电表
        /// <summary>
        /// 执行循环命令
        /// 连接的采集器，间隔抄表
        /// </summary>
        private void DoLoopMeters()
        {
            while (true)
            {
                var _appService = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();

                //查询采集器和电表，
                //根据设置的采集周期和上次完成时间，决定是否轮询
                var list = _appService.GetCollectors(_settings.Value.MeterInterval, _settings.Value.BathCount, 0);
                if (list.Count == 0)
                {
                    //如果都没有到抄表时间，计算最近一个抄表时间，作为等待执行
                    var nearest = _appService.GetNearestTime();
                    var ticks = (nearest.AddMinutes(_settings.Value.MeterInterval).Ticks - DateTime.Now.Ticks) / 10000;
                    if (ticks > 0)
                    {
                        Task.Delay((int)ticks).Wait();
                        continue;
                    }
                }
                while (_Clients.Count == 0)
                {
                    Task.Delay(100).Wait();
                }

                //_logger.LogDebug($"--采集器数量 {list.Count}--Socket数量 {_Clients.Count}--");

                var pll = Parallel.ForEach(list, (collector, pl) =>
                {
                    if (_Clients.TryRemove(collector.CollectorCode, out Socket mySocket))
                    {
                        _logger.LogDebug($"--采集器 {collector.CollectorCode} 开始工作--");

                        var service = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();
                        foreach (var meter in collector.Meters)
                        {
                            var result = _socketor.Send(mySocket, meter.CollectorCode, meter.MeterCode, "9010", meter.Password);
                            if (result.Result && result.MeterCode == meter.MeterCode)
                            {
                                var datetime = DateTime.Now;
                                //读表信息插入
                                var record = new MeterRecordDto
                                {
                                    AppId = meter.AppId,
                                    CollectorCode = meter.CollectorCode,
                                    MeterCode = meter.MeterCode,
                                    ActivePower = result.Power,
                                    TakeTime = datetime,
                                };
                                service.AddMeterRecord(record);

                                _ = DataChangedAsync(meter, datetime, result.Power);
                            }
                            else
                            {
                                //异常记录日志，日常周期性，不用放入队列
                                _logger.LogWarning($"抄表异常：{meter.CollectorCode}_{meter.MeterCode},Message:{result.Message}");
                                var log = new ExceptionLogDto
                                {
                                    AppId = meter.AppId,
                                    CollectorCode = meter.CollectorCode,
                                    MeterCode = meter.MeterCode,
                                    CallbackUrl = "",
                                    Command = "9010",
                                    Message = result.Message,
                                    StackTrace = "",
                                    RequestId = ""
                                };
                                service.AddExceptionLog(log);
                            }
                            service.UpdateMeter(meter.Id, result.Power);
                        }
                        _Clients.TryAdd(collector.CollectorCode, mySocket);
                        _logger.LogDebug($"--采集器 {collector.CollectorCode} 工作完成，连接池内数量 {_Clients.Count}--");
                        service.UpdateCollector(collector.Id);

                    }
                    else
                    {
                        //  _logger.LogInformation($"采集器 {collector.CollectorCode} 未连接");
                    }
                });
                Task.Delay(1000).Wait();
            }
        }
        #endregion

        #region 读表数据变化通知
        /// <summary>
        /// 执行DataChanged Notify
        /// </summary>
        /// <param name="dto"></param>
        /// <param name="time"></param>
        /// <param name="power"></param>
        /// <returns></returns>
        private async Task DataChangedAsync(MeterDto dto, DateTime time, decimal power)
        {
            var client = _httpClientFactory.CreateClient();
            var timestamp = DateTime.Now.Ticks.ToString();
            var nonce = new Random().Next(100000, 999999).ToString();
            string[] param = new string[] { dto.AppToken, timestamp, nonce };
            string signature = Utility.GetSha1Hash(string.Join("", param));
            var result = new IoTDataChangedResult
            {
                CollectorCode = dto.CollectorCode,
                MeterCode = dto.MeterCode,
                ExecuteTime = time,
                Data = new Dictionary<string, dynamic> { { "Power", power }, { "MeterStatus", dto.Status } }
            };
            var callbackurl = string.Empty;
            try
            {
                var body = JsonConvert.SerializeObject(result, new JsonSerializerSettings()
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                });
                foreach (var item in dto.Subscribes)
                {
                    callbackurl = item.CallbackUrl;
                    if (item.NotifyType == IoTNotifyType.DeviceDataChanged)
                    {
                        var response = await client.PostAsync($"{item.CallbackUrl}?signature={signature}&timestamp={timestamp}&nonce={nonce}", new StringContent(body));
                        _logger.LogDebug($"DataChanged 通知结果:{response.StatusCode}");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "DataChangedAsync");

                var log = new ExceptionLogDto
                {
                    AppId = dto.AppId,
                    CollectorCode = dto.CollectorCode,
                    MeterCode = dto.MeterCode,
                    CallbackUrl = callbackurl,
                    Command = "9010",
                    Message = ex.Message,
                    StackTrace = ex.StackTrace,
                    RequestId = ""
                };

                _serviceProvider.CreateScope().ServiceProvider
                    .GetRequiredService<IPowerDataService>()
                    .AddExceptionLog(log);
            }
        }
        #endregion

        #region 命令状态变化通知
        /// <summary>
        /// 命令状态变化
        /// </summary>
        /// <param name="dto"></param>
        /// <param name="meterstatus"></param>
        /// <param name="time"></param>
        /// <param name="power"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task CommandChangedAsync(OperationDto dto, int meterstatus, DateTime time, decimal power, string message)
        {
            var client = _httpClientFactory.CreateClient();
            var timestamp = DateTime.Now.Ticks.ToString();
            var nonce = new Random().Next(100000, 999999).ToString();
            string[] param = new string[] { dto.AppToken, timestamp, nonce };
            string signature = Utility.GetSha1Hash(string.Join("", param));
            var result = new IoTCmdChangedResult
            {
                CollectorCode = dto.CollectorCode,
                MeterCode = dto.MeterCode,
                ExecuteTime = time,
                RequestId = dto.RequestId,
                Command = dto.Command,
                Status = dto.Status,
                Message = dto.Message,
                Data = new Dictionary<string, dynamic> { { "Power", power }, { "Message", message }, { "MeterStatus", meterstatus } }
            };
            var body = JsonConvert.SerializeObject(result, new JsonSerializerSettings()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            });

            var callbackurl = string.Empty;
            try
            {
                if (!string.IsNullOrEmpty(dto.CallbackUrl))
                    await client.PostAsync($"{dto.CallbackUrl}?signature={signature}&timestamp={timestamp}&nonce={nonce}", new StringContent(body));

                foreach (var item in dto.Subscribes)
                {
                    if (item.NotifyType == IoTNotifyType.CommandStateChanged)
                        await client.PostAsync($"{item.CallbackUrl}?signature={signature}&timestamp={timestamp}&nonce={nonce}", new StringContent(body));
                }

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "CommandChangedAsync");

                var log = new ExceptionLogDto
                {
                    AppId = dto.AppId,
                    CollectorCode = dto.CollectorCode,
                    MeterCode = dto.MeterCode,
                    CallbackUrl = callbackurl,
                    Command = dto.Command,
                    Message = ex.Message,
                    StackTrace = ex.StackTrace,
                    RequestId = ""
                };

                _serviceProvider.CreateScope().ServiceProvider
                    .GetRequiredService<IPowerDataService>()
                    .AddExceptionLog(log);
            }
        }
        #endregion

        #region 即时执行外部命令
        /// <summary>
        /// 执行命令
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public async Task<IoTCmdResult> DeviceCommandAsync(IoTCmdReq req)
        {
            IoTCmdResult result = new IoTCmdResult { Data = new Dictionary<string, dynamic>() };
            var dbService = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();
            var meter = dbService.GetMeter(req.MeterCode);
            if (meter == null)
            {
                result.Status = IoTCmdStatus.CANCELED;
                result.Message = "电表还没有注册";
                return result;
            }
            var power = 0.0M;
            //找到Socket同步执行，找不到放到队列异步执行
            if (_Clients.TryRemove(meter.CollectorCode, out Socket mysocket))
            {
                var ret = _socketor.Send(mysocket, meter.CollectorCode, req.Command, meter.MeterCode, meter.Password);

                //解析返回内容
                //成功直接返回，失败加入队列等待下一次
                if (ret.Result && ret.MeterCode == meter.MeterCode)
                {
                    result.Status = IoTCmdStatus.SUCCESSFUL;
                    result.ExecuteTime = DateTime.Now;

                    switch (req.Command.ToUpper())
                    {
                        case "C03C":

                            break;
                        case "C03D":

                            break;
                        case "9010":
                            //读表信息插入
                            power = ret.Power;
                            result.Data.Add("Power", ret.Power);
                            var record = new MeterRecordDto
                            {
                                AppId = meter.AppId,
                                CollectorCode = meter.CollectorCode,
                                MeterCode = meter.MeterCode,
                                ActivePower = ret.Power,
                                TakeTime = DateTime.Now,
                            };
                            dbService.AddMeterRecord(record);
                            break;

                        default:
                            _logger.LogWarning($"下发命令[{req.Command}]还未实现");
                            break;
                    }
                }
                else
                {
                    //放入队列
                    result.Status = IoTCmdStatus.UnSend;
                    result.Message = ret.Message;
                }

                //工作完成放回去
                if (_Clients.TryAdd(meter.CollectorCode, mysocket))
                { }
            }
            else
            {
                //放入队列
                result.Status = IoTCmdStatus.PENDING;
            }
            //无论是否成功都放入队列
            var op = new OperationDto
            {
                AppId = req.AppId,
                CollectorCode = meter.CollectorCode,
                MeterCode = meter.MeterCode,
                RequestId = req.RequestId,
                Command = req.Command,
                CallbackUrl = req.CallbackUrl,
                Password = meter.Password,
                Status = result.Status,
                TryCount = result.Status == IoTCmdStatus.SUCCESSFUL ? 0 : 1,
                CreateTime = DateTime.Now,
                ExcuteTime = DateTime.Now,
                QueryId = Guid.NewGuid().ToString(),
                ActivePower = power
            };
            dbService.AddOperation(op);
            await Task.CompletedTask;
            return result;
        }
        /// <summary>
        /// 查询命令执行结果
        /// </summary>
        /// <param name="queryId"></param>
        /// <returns></returns>
        public IoTCmdResult QueryDeviceCommand(string queryId)
        {
            var dbService = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IPowerDataService>();

            var op = dbService.GetOperation(queryId);

            return new IoTCmdResult
            {
                QueryId = op.QueryId,
                ExecuteTime = op.ExcuteTime,
                Status = op.Status,
                Message = op.Message,
                Data = new Dictionary<string, dynamic> { { "Power", op.ActivePower } }

            };

        }
        #endregion

    }
}
